/*
* Copyright 2012, CMM, University of Queensland.
*
* This file is part of Paul.
*
* Paul is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Paul is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Paul. If not, see <http://www.gnu.org/licenses/>.
*/
package au.edu.uq.cmm.paul.grabber;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.security.MessageDigest;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.hibernate.annotations.GenericGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import au.edu.uq.cmm.paul.PaulException;
/**
* This class represents the administrative metadata for a dataset captured
* by the FileGrabber.
*
* @author scrawley
*/
@Entity
@Table(name = "DATASET_METADATA",
uniqueConstraints=@UniqueConstraint(columnNames={"recordUuid"}))
public class DatasetMetadata {
private static final Logger LOG = LoggerFactory.getLogger(DatasetMetadata.class);
private String userName;
private Long facilityId;
private String facilityName;
private String accountName;
private String sourceFilePathnameBase;
private String facilityFilePathnameBase;
private Date captureTimestamp;
private Date updateTimestamp;
private Date sessionStartTimestamp;
private String metadataFilePathname;
private Long id;
private String sessionUuid;
private String recordUuid;
private String emailAddress;
private String operatorName;
private String datasetHash;
private List<DatafileMetadata> datafiles;
public DatasetMetadata() {
super();
}
public DatasetMetadata(String sourceFilePathnameBase,
String facilityFilePathnameBase,
String metadataFilePathname, String userName,
String facilityName, Long facilityId,
String accountName, String emailAddress,
String operatorName, Date captureTimestamp,
String sessionUuid, Date sessionStartTimestamp,
List<DatafileMetadata> datafiles) {
super();
this.sourceFilePathnameBase = sourceFilePathnameBase;
this.facilityFilePathnameBase = facilityFilePathnameBase;
this.metadataFilePathname = metadataFilePathname;
this.userName = userName;
this.facilityName = facilityName;
this.facilityId = facilityId;
this.accountName = accountName;
this.emailAddress = emailAddress;
this.operatorName = operatorName;
this.captureTimestamp = captureTimestamp;
this.updateTimestamp = captureTimestamp;
this.sessionStartTimestamp = sessionStartTimestamp;
this.sessionUuid = sessionUuid;
this.recordUuid = UUID.randomUUID().toString();
this.datafiles = datafiles;
}
@Id
@GeneratedValue(generator = "increment")
@GenericGenerator(name = "increment", strategy = "increment")
@JsonIgnore
public Long getId() {
return id;
}
public String getUserName() {
return userName;
}
public String getFacilityName() {
return facilityName;
}
public String getAccountName() {
return accountName;
}
@Temporal(TemporalType.TIMESTAMP)
public Date getCaptureTimestamp() {
return captureTimestamp;
}
@Temporal(TemporalType.TIMESTAMP)
public Date getSessionStartTimestamp() {
return sessionStartTimestamp;
}
@JsonIgnore
@Transient
public Date getFirstFileTimestamp() {
if (datafiles.isEmpty()) {
return null;
} else {
Date min = null;
for (DatafileMetadata datafile : datafiles) {
if (min == null) {
min = datafile.getFileWriteTimestamp();
} else {
Date tmp = datafile.getFileWriteTimestamp();
if (tmp != null && tmp.getTime() < min.getTime()) {
min = tmp;
}
}
}
return min;
}
}
@JsonIgnore
@Transient
public Date getLastFileTimestamp() {
if (datafiles.isEmpty()) {
return null;
} else {
Date max = null;
for (DatafileMetadata datafile : datafiles) {
if (max == null) {
max = datafile.getFileWriteTimestamp();
} else {
Date tmp = datafile.getFileWriteTimestamp();
if (tmp != null && tmp.getTime() > max.getTime()) {
max = tmp;
}
}
}
return max;
}
}
public void setUserName(String userName) {
this.userName = userName;
}
public void setFacilityName(String facilityName) {
this.facilityName = facilityName;
}
public void setAccountName(String accountName) {
this.accountName = accountName;
}
public void setCaptureTimestamp(Date captureTimestamp) {
this.captureTimestamp = captureTimestamp;
}
public void setSessionStartTimestamp(Date sessionStartTimestamp) {
this.sessionStartTimestamp = sessionStartTimestamp;
}
public void setId(Long id) {
this.id = id;
}
public String getSessionUuid() {
return sessionUuid;
}
public void setSessionUuid(String uuid) {
this.sessionUuid = uuid;
}
public String getRecordUuid() {
return recordUuid;
}
public void setRecordUuid(String recordUuid) {
this.recordUuid = recordUuid;
}
public String getMetadataFilePathname() {
return metadataFilePathname;
}
public void setMetadataFilePathname(String metadataFilePathname) {
this.metadataFilePathname = metadataFilePathname;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public String getSourceFilePathnameBase() {
return sourceFilePathnameBase;
}
public void setSourceFilePathnameBase(String sourceFilePathnameBase) {
this.sourceFilePathnameBase = sourceFilePathnameBase;
}
public String getFacilityFilePathnameBase() {
return facilityFilePathnameBase;
}
public void setFacilityFilePathnameBase(String facilityFilePathnameBase) {
this.facilityFilePathnameBase = facilityFilePathnameBase;
}
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@OrderBy(value="fileWriteTimestamp ASC")
@JoinColumn(name="datafile_id")
public List<DatafileMetadata> getDatafiles() {
return datafiles;
}
public void setDatafiles(List<DatafileMetadata> datafiles) {
this.datafiles = datafiles;
}
public String getOperatorName() {
return operatorName;
}
public void setOperatorName(String operatorName) {
this.operatorName = operatorName;
}
public Long getFacilityId() {
return facilityId;
}
public void setFacilityId(Long facilityId) {
this.facilityId = facilityId;
}
public Date getUpdateTimestamp() {
return updateTimestamp;
}
public void setUpdateTimestamp(Date updateTimestamp) {
this.updateTimestamp = updateTimestamp;
}
public String getDatasetHash() {
return datasetHash;
}
public void setDatasetHash(String hash) {
this.datasetHash = (hash != null && hash.isEmpty()) ? null : hash;
}
@Transient
public String getCombinedDatafileHash() {
String hash = null;
for (DatafileMetadata datafile : datafiles) {
try {
hash = HashUtils.combineHashes(hash, datafile.getDatafileHash());
} catch (InvalidHashException ex) {
LOG.warn("Skipped an invalid hash for datafile " + datafile);
}
}
return hash;
}
public void checkHashes(boolean includeDatasetHash) throws IncorrectHashException {
for (DatafileMetadata datafile : datafiles) {
datafile.checkDatafileHash();
}
if (includeDatasetHash && datasetHash != null) {
String tmp = calculateDatasetHash();
if (!datasetHash.equals(tmp)) {
throw new IncorrectHashException("Dataset hash is incorrect", datasetHash, tmp);
}
}
}
public void updateDatasetHash() {
for (DatafileMetadata datafile : datafiles) {
datafile.updateDatafileHash();
}
setDatasetHash(calculateDatasetHash());
}
private String calculateDatasetHash() {
StringWriter sw = new StringWriter();
// Note that the hash excludes the 'datasetHash' field ...
String savedHash = datasetHash;
try {
datasetHash = null;
serialize(sw);
byte[] data = sw.toString().getBytes("UTF-8");
MessageDigest md = HashUtils.createDigester();
md.update(data);
byte[] hash = md.digest();
return HashUtils.bytesToHexString(hash);
} catch (IOException ex) {
throw new PaulException("Impossible exception", ex);
} finally {
datasetHash = savedHash;
}
}
public void serialize(Writer writer) throws IOException {
try {
ObjectMapper mapper = new ObjectMapper();
JsonFactory jf = new JsonFactory();
JsonGenerator jg = jf.createJsonGenerator(writer);
jg.useDefaultPrettyPrinter();
mapper.writeValue(jg, this);
} catch (JsonParseException ex) {
throw new PaulException(ex);
} catch (JsonMappingException ex) {
throw new PaulException(ex);
}
}
public String toString() {
StringWriter sw = new StringWriter();
try {
serialize(sw);
} catch (IOException ex) {
throw new PaulException("Impossible exception", ex);
}
return sw.toString();
}
}